using System;
using System.Text;
using System.Xml;
using ICSharpCode.TextEditor.Formatting.TSqlFormatter.Interfaces;

namespace ICSharpCode.TextEditor.Formatting.TSqlFormatter.Formatters
{
    public class TSqlIdentityFormatter : ISqlTokenFormatter, ISqlTreeFormatter
    {
        public bool HTMLColoring { get; set; }

        public bool HTMLFormatted => HTMLColoring;

        public string ErrorOutputPrefix { get; set; }

        public TSqlIdentityFormatter()
        {
        }

        public TSqlIdentityFormatter(bool htmlColoring)
        {
            HTMLColoring = htmlColoring;
            ErrorOutputPrefix = "--WARNING! ERRORS ENCOUNTERED DURING SQL PARSING!" + Environment.NewLine;
        }

        public string FormatSQLTree(XmlDocument sqlTreeDoc)
        {
            string arg = "SqlRoot";
            BaseFormatterState baseFormatterState = new BaseFormatterState(HTMLColoring);
            if (sqlTreeDoc.SelectSingleNode(string.Format("/{0}/@{1}[.=1]", arg, "errorFound")) != null)
            {
                baseFormatterState.AddOutputContent(ErrorOutputPrefix);
            }
            XmlNodeList nodes = sqlTreeDoc.SelectNodes($"/{arg}/*");
            return FormatSQLNodes(nodes, baseFormatterState);
        }

        public string FormatSQLTree(XmlNode sqlTreeFragment)
        {
            BaseFormatterState state = new BaseFormatterState(HTMLColoring);
            return FormatSQLNodes(sqlTreeFragment.SelectNodes("."), state);
        }

        private static string FormatSQLNodes(XmlNodeList nodes, BaseFormatterState state)
        {
            ProcessSqlNodeList(state, nodes);
            return state.DumpOutput();
        }

        private static void ProcessSqlNodeList(BaseFormatterState state, XmlNodeList rootList)
        {
            foreach (XmlElement root in rootList)
            {
                ProcessSqlNode(state, root);
            }
        }

        private static void ProcessSqlNode(BaseFormatterState state, XmlElement contentElement)
        {
            if (contentElement.GetAttribute("hasError") == "1")
            {
                state.OpenClass("SQLErrorHighlight");
            }
            switch (contentElement.Name)
            {
                case "DDLDetailParens":
                case "DDLParens":
                case "FunctionParens":
                case "InParens":
                case "ExpressionParens":
                case "SelectionTargetParens":
                    state.AddOutputContent("(");
                    ProcessSqlNodeList(state, contentElement.SelectNodes("*"));
                    state.AddOutputContent(")");
                    break;
                case "SqlRoot":
                case "SqlStatement":
                case "Clause":
                case "BooleanExpression":
                case "DDLProceduralBlock":
                case "DDLOtherBlock":
                case "DDLDeclareBlock":
                case "CursorDeclaration":
                case "BeginEndBlock":
                case "TryBlock":
                case "CatchBlock":
                case "CaseStatement":
                case "Input":
                case "When":
                case "Then":
                case "CaseElse":
                case "IfStatement":
                case "ElseClause":
                case "WhileLoop":
                case "DDLAsBlock":
                case "Between":
                case "LowerBound":
                case "UpperBound":
                case "CTEWithClause":
                case "CTEAlias":
                case "CTEAsBlock":
                case "CursorForBlock":
                case "CursorForOptions":
                case "TriggerCondition":
                case "CompoundKeyword":
                case "BeginTransaction":
                case "RollbackTransaction":
                case "SaveTransaction":
                case "CommitTransaction":
                case "BatchSeparator":
                case "SetOperatorClause":
                case "ContainerOpen":
                case "ContainerMultiStatementBody":
                case "ContainerSingleStatementBody":
                case "ContainerContentBody":
                case "ContainerClose":
                case "SelectionTarget":
                case "PermissionsBlock":
                case "PermissionsDetail":
                case "PermissionsTarget":
                case "PermissionsRecipient":
                case "DDLWith":
                case "MergeClause":
                case "MergeTarget":
                case "MergeUsing":
                case "MergeCondition":
                case "MergeWhen":
                case "MergeThen":
                case "MergeAction":
                case "JoinOn":
                    foreach (XmlNode childNode in contentElement.ChildNodes)
                    {
                        switch (childNode.NodeType)
                        {
                            case XmlNodeType.Element:
                                ProcessSqlNode(state, (XmlElement)childNode);
                                break;
                            default:
                                throw new Exception("Unexpected xml node type encountered!");
                            case XmlNodeType.Text:
                            case XmlNodeType.Comment:
                                break;
                        }
                    }
                    break;
                case "MultiLineComment":
                    state.AddOutputContent("/*" + contentElement.InnerText + "*/", "SQLComment");
                    break;
                case "SingleLineComment":
                    state.AddOutputContent("--" + contentElement.InnerText, "SQLComment");
                    break;
                case "SingleLineCommentCStyle":
                    state.AddOutputContent("//" + contentElement.InnerText, "SQLComment");
                    break;
                case "String":
                    state.AddOutputContent("'" + contentElement.InnerText.Replace("'", "''") + "'", "SQLString");
                    break;
                case "NationalString":
                    state.AddOutputContent("N'" + contentElement.InnerText.Replace("'", "''") + "'", "SQLString");
                    break;
                case "QuotedString":
                    state.AddOutputContent("\"" + contentElement.InnerText.Replace("\"", "\"\"") + "\"");
                    break;
                case "BracketQuotedName":
                    state.AddOutputContent("[" + contentElement.InnerText.Replace("]", "]]") + "]");
                    break;
                case "Comma":
                case "Period":
                case "Semicolon":
                case "Asterisk":
                case "EqualsSign":
                case "ScopeResolutionOperator":
                case "And":
                case "Or":
                case "AlphaOperator":
                case "OtherOperator":
                    state.AddOutputContent(contentElement.InnerText, "SQLOperator");
                    break;
                case "FunctionKeyword":
                    state.AddOutputContent(contentElement.InnerText, "SQLFunction");
                    break;
                case "OtherKeyword":
                case "DataTypeKeyword":
                case "DDLReturns":
                case "PseudoName":
                    state.AddOutputContent(contentElement.InnerText, "SQLKeyword");
                    break;
                case "Other":
                case "WhiteSpace":
                case "NumberValue":
                case "MonetaryValue":
                case "BinaryValue":
                case "Label":
                    state.AddOutputContent(contentElement.InnerText);
                    break;
                default:
                    throw new Exception("Unrecognized element in SQL Xml!");
            }
            if (contentElement.GetAttribute("hasError") == "1")
            {
                state.CloseClass();
            }
        }

        public string FormatSQLTokens(ITokenList sqlTokenList)
        {
            StringBuilder stringBuilder = new StringBuilder();
            if (sqlTokenList.HasUnfinishedToken)
            {
                stringBuilder.Append(ErrorOutputPrefix);
            }
            foreach (IToken sqlToken in sqlTokenList)
            {
                switch (sqlToken.Type)
                {
                    case SqlTokenType.MultiLineComment:
                        stringBuilder.Append("/*");
                        stringBuilder.Append(sqlToken.Value);
                        stringBuilder.Append("*/");
                        break;
                    case SqlTokenType.SingleLineComment:
                        stringBuilder.Append("--");
                        stringBuilder.Append(sqlToken.Value);
                        break;
                    case SqlTokenType.SingleLineCommentCStyle:
                        stringBuilder.Append("//");
                        stringBuilder.Append(sqlToken.Value);
                        break;
                    case SqlTokenType.String:
                        stringBuilder.Append("'");
                        stringBuilder.Append(sqlToken.Value.Replace("'", "''"));
                        stringBuilder.Append("'");
                        break;
                    case SqlTokenType.NationalString:
                        stringBuilder.Append("N'");
                        stringBuilder.Append(sqlToken.Value.Replace("'", "''"));
                        stringBuilder.Append("'");
                        break;
                    case SqlTokenType.QuotedString:
                        stringBuilder.Append("\"");
                        stringBuilder.Append(sqlToken.Value.Replace("\"", "\"\""));
                        stringBuilder.Append("\"");
                        break;
                    case SqlTokenType.BracketQuotedName:
                        stringBuilder.Append("[");
                        stringBuilder.Append(sqlToken.Value.Replace("]", "]]"));
                        stringBuilder.Append("]");
                        break;
                    case SqlTokenType.OpenParens:
                    case SqlTokenType.CloseParens:
                    case SqlTokenType.WhiteSpace:
                    case SqlTokenType.OtherNode:
                    case SqlTokenType.Comma:
                    case SqlTokenType.Period:
                    case SqlTokenType.Semicolon:
                    case SqlTokenType.Colon:
                    case SqlTokenType.Asterisk:
                    case SqlTokenType.EqualsSign:
                    case SqlTokenType.MonetaryValue:
                    case SqlTokenType.Number:
                    case SqlTokenType.BinaryValue:
                    case SqlTokenType.OtherOperator:
                    case SqlTokenType.PseudoName:
                        stringBuilder.Append(sqlToken.Value);
                        break;
                    default:
                        throw new Exception("Unrecognized Token Type in Token List!");
                }
            }
            return stringBuilder.ToString();
        }
    }
}
